package physics;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import physics.component.FixedMass;
import physics.component.Mass;
import physics.component.Spring;
import physics.globalforce.GlobalForce;
import ui.Canvas;


/**
 * Simulates objects moving around in a bounded environment.
 *
 * @author Robert C. Duvall
 */
public class Simulation {
    private static final int STATUS_OFFSET = 12;

    private final List<Assembly> myAssemblies;
    private final List<GlobalForce> myGlobalForces;
    private final Canvas myContainer;
    private Assembly myUserAssembly;
    private Mass myUserMass;
    private Spring myUserSpring;

    /**
     * Create a Canvas with the given size.
     *
     * @param container The containing canvas for the simulation
     */
    public Simulation (final Canvas container) {
        myAssemblies = new ArrayList<Assembly>();
        myGlobalForces = new ArrayList<GlobalForce>();
        myContainer = container;
    }

    /**
     * Attaches a fixed mass to the given x and y coordinates, and attaches a
     * spring to the nearest mass in any assembly.
     *
     * @param x x-coordinate for fixed mass
     * @param y y-coordinate for fixed mass
     */
    public void addUserMouseMass (int x, int y) {
        myUserMass = new FixedMass(Integer.MIN_VALUE, x, y, 0);
        double closestDistance = Double.MAX_VALUE;
        Mass closestMass = null;
        for (Assembly a : myAssemblies) {
            Mass closestMassInThisAssembly = a.getClosestMass(myUserMass);
            double distanceToCurrentAssembly =
                    closestMassInThisAssembly.getDistanceBetween(myUserMass);
            if (distanceToCurrentAssembly < closestDistance) {
                closestDistance = distanceToCurrentAssembly;
                closestMass = closestMassInThisAssembly;
                myUserAssembly = a;
            }
        }
        if (closestMass != null) {
            myUserSpring = new Spring(myUserMass, closestMass);
            myUserAssembly.add(myUserMass);
            myUserAssembly.add(myUserSpring);
        }
    }

    /**
     * Removes the user placed mass and spring.
     */
    public void removeUserMouseMass () {
        if (myUserMass != null && myUserSpring != null) {
            myUserAssembly.remove(myUserMass);
            myUserAssembly.remove(myUserSpring);
        }
    }

    /**
     * Returns the user placed mass.
     *
     * @return The mass that the user placed
     */
    public Mass getUserMouseMass () {
        return myUserMass;
    }

    /**
     * Removes all assemblies from the simulation, and therefore masses and
     * springs as well.
     */
    public final void clear () {
        myAssemblies.clear();
    }

    /**
     * Removes all global forces from current simulation.
     */
    public final void clearGlobalForces () {
        myGlobalForces.clear();
    }

    /**
     * Paint all shapes on the canvas.
     *
     * @param pen
     *        used to paint shape on the screen
     */
    public final void paint (final Graphics2D pen) {
        for (Assembly a : myAssemblies) {
            a.paint(pen);
        }
        pen.setColor(Color.BLACK);
        int currentOffset = 2 * STATUS_OFFSET;
        for (String s : EnvironmentSingleton.getInstance().getStateToPrint()) {
            pen.drawString(s, STATUS_OFFSET, currentOffset);
            currentOffset += STATUS_OFFSET;
        }
    }

    /**
     * Called by each step of timer, multiple times per second.
     *
     * This should update the state of the animated shapes by just a little so
     * they appear to move over time.
     *
     * @param dt Change in time
     */
    public final void update (final double dt) {
        for (Assembly a : myAssemblies) {
            a.updateMovers(dt);
            for (GlobalForce gf : myGlobalForces) {
                a.applyGlobalForce(gf);
            }
        }
    }

    /**
     * Returns size (in pixels) of the game area.
     *
     * @return Dimensions of the game area
     */
    public final Dimension getSize () {
        return myContainer.getSize();
    }

    /**
     * Create a new assembly using the current simulation as a context. In
     * addition, return the reference to the created object.
     *
     * @return New assembly object
     */
    public final Assembly createAndGetNewAssembly () {
        Assembly a = new Assembly(this);
        myAssemblies.add(a);
        return a;
    }

    /**
     * Retrieves the current assembly, which is assumed to be the last index.
     *
     * @return Last assembly added to the simulation
     */
    public final Assembly getCurrentAssembly () {
        return myAssemblies.get(myAssemblies.size() - 1);
    }

    /**
     * Adds a GlobalForce to the environment.
     *
     * @param globalForces A parameter list of GlobalForces to add
     */
    public final void addGlobalForce (final GlobalForce ... globalForces) {
        for (GlobalForce gf : globalForces) {
            myGlobalForces.add(gf);
        }
    }
}
